#include "OgreRoot.h"
#include "OgreException.h"
#include "OgreLogManager.h"
#include "OgreStringConverter.h"
#include "OgreWindowEventUtilities.h"

#include "OgreGLESPrerequisites.h"
#include "OgreGLESRenderSystem.h"

#include "OgreEGLSupport.h"
#include "OgreEGLWindow.h"
#include "OgreEGLContext.h"
#include "OgreGLESPixelFormat.h"

#include <iostream>
#include <algorithm>
#include <climits>

namespace Ogre
{
EGLWindow::EGLWindow(EGLSupport* glsupport)
	: mGLSupport(glsupport),
	  mContext(0),
	  mEglConfig(0),
	  mEglSurface(0),
	  mWindow(0),
	  mNativeDisplay(0),
	  mEglDisplay(EGL_NO_DISPLAY)
	{
		mIsTopLevel = false;
		mIsFullScreen = false;
		mClosed = false;
		mActive = true;
		mIsExternalGLControl = false;
		mVisible = false;
	}
	
	EGLWindow::~EGLWindow()
	{
		destroy();
		
		if (mContext)
		{
			delete mContext;
		}
		
		mContext = 0;
	}
	
	void EGLWindow::destroy(void)
	{
		if (mClosed)
		{
			return;
		}
		
		mClosed = true;
		mActive = false;
		
		if (!mIsExternal)
		{
			WindowEventUtilities::_removeRenderWindow(this);
		}
		
		if (mIsFullScreen)
		{
			mGLSupport->switchMode();
			switchFullScreen(false);
		}
	}
	
	void EGLWindow::setFullscreen(bool fullscreen, uint width, uint height)
	{
		short frequency = 0;
		
		if (mClosed || !mIsTopLevel)
		{
			return;
		}
		
		if (fullscreen == mIsFullScreen && width == mWidth && height == mHeight)
		{
			return;
		}
		if (fullscreen)
		{
			mGLSupport->switchMode(width, height, frequency);
		}
		else
		{
			mGLSupport->switchMode();
		}
		
		if (mIsFullScreen != fullscreen)
		{
			switchFullscreen(fullscreen);
		}
		
		if (!mIsFullScreen)
		{
			resize(width, height);
			reposition(mLeft, mTop);
		}
	}
	
	bool EGLWindow::isClosed() const
	{
		return mClosed;
	}
	
	bool EGLWindow::isVisible() const
	{
		return mVisible;
	}
	
	void EGLWindow::setVisible(bool visible)
	{
		mVisible = visible;
	}
	
	void EGLWindow::swapBuffers(bool waitForVSync)
	{
		if (mClosed || mIsExternalGLControl)
		{
			return;
		}
		
		glFlush();
		if (eglSwapBuffers(mEglDisplay, mEglSurface) == EGL_FALSE)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR,
						"Fail to SwapBuffers",
						__FUNCTION__);
		}
	}
	
	void EGLWindow::getCustomAttribute(const String& name, void* pData)
	{
		if (name == "DISPLAYNAME")
		{
			*static_cast<String*>(pData) = mGLSupport->getDisplayName();
			return;
		}
		else if (name == "DISPLAY")
		{
			*static_cast<EGLDisplay*>(pData) = mEglDisplay;
			return;
		}
		else if (name == "GLCONTEXT")
		{
			*static_cast<EGLContext**>(pData) = mContext;
			return;
		}
		else if (name == "WINDOW")
		{
			*static_cast<NativeWindowType*>(pData) = mWindow;
			return;
		}
	}
	
	void EGLWindow::copyContentsToMemory(const PixelBox& dst, FrameBuffer buffer)
	{
		if ((dst.left < 0) || (dst.right > mWidth) ||
			(dst.top < 0) || (dst.bottom > mHeight) ||
			(dst.front != 0) || (dst.back != 1))
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
				"Invalid box.",
				"Win32Window::copyContentsToMemory");
		}
		
		if (buffer == FB_AUTO)
		{
			buffer = mIsFullScreen ? FB_FRONT : FB_BACK;
		}
		
		GLenum format = GLESPixelUtil::getGLOriginFormat(dst.format);
		GLenum type = GLESPixelUtil::getGLOriginDataType(dst.format);
		
		if ((format == 0) || (type == 0))
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
				"Unsupported format.",
				"GtkEGLWindow::copyContentsToMemory");
		}
		
		RenderSystem* rsys = Root::getSingleton().getRenderSystem();
		rsys->setViewport(this->getViewport(0));
		
		glPixelStorei(GL_PACK_ALIGNMENT, 1);
		
		glReadPixels((GLint)dst.left, (GLint)dst.top,
			(GLsizei)dst.getWidth(), (GLsizei)dst.getHeight(),
			format, type, dst.data);
			
		glPixelStorei(GL_PACK_ALIGNMENT, 4);
		
		{
			size_t rowSpan = dst.getWidth() * PixelUtil::getNumElemBytes(dst.format);
			size_t height = dst.getHeight();
			uchar* tmpData = new uchar[rowSpan * height];
			uchar* srcRow = (uchar*)dst.data, *tmpRow = tmpData + (height - 1) * rowSpan;
			
			while (tmpRow >= tmpData)
			{
				memcpy(tmpRow, srcRow, rowSpan);
				srcRow += rowSpan;
				tmpRow -= rowSpan;
			}
			memcpy(dst.data, tmpData, rowSpan * height);
			
			delete[] tmpData;
		}
	}
	
	::EGLSurface EGLWindow::createSurfaceFromWindow(::EGLDisplay display,
													NativeWindowType win)
	{
		::EGLSurface surface;
		
		surface = eglCreateWindowSurface(display, mEglConfig, win, NULL);
		
		if (surface == EGL_NO_SURFACE)
		{
			OGRE_EXCEPT(Exception::ERR_RENDERINGAPI_ERROR,
						"Fail to create	EGLSurface based on X NativeWindowType",
						__FUNCTION__);
		}
		return surface;
	}
	
	bool EGLWindow::requiresTextureFlipping() const
	{
		return false;
	}
}
